package com.training.hatton.dropboxclient.images;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

import org.apache.http.client.methods.HttpGet;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.support.v4.util.LruCache;
import android.util.Log;
import android.util.SparseArray;
import android.widget.BaseAdapter;
import android.widget.ImageView;

import com.training.hatton.dropboxclient.http.HttpManager;
import com.training.hatton.dropboxclient.utill.DropboxService;
import com.training.hatton.dropboxclient.utill.MD5;
import com.training.hatton.dropboxclient.utill.Signer;

public class ImageLoader {
	
	private static final int QUEUE_LIMIT = 30;
	private static final int POOL_SIZE = 4;
	public static final String IMAGE_LOADER = "DROPBOX IMAGE_LOADER";
	private static final int SIZE_CACHE = 4 * 1024 * 1024;
	
	private Context mContext;
	private LruCache<String, Bitmap> mLruCashe;
	private List<ImageLoadCallback> mUrlQueue;
	private ExecutorService mExecutorService;
	private Handler mImageHandler;
	private SparseArray<String> mCurrentImageViewUrls;
	private boolean lock;
	
	public interface ImageLoadCallback {
		
		String getUrl();
		
		void onSuccess(Bitmap bm);

		void onError(Exception e);
	}
	
	
	public ImageLoader(Context ctx) {
		lock = false;
		mImageHandler = new Handler();
		mExecutorService = Executors.newFixedThreadPool(POOL_SIZE);; 
		mUrlQueue = Collections.synchronizedList(new ArrayList<ImageLoadCallback>());
		mContext = ctx;
		mCurrentImageViewUrls = new SparseArray<String>();
		mLruCashe = new LruCache<String, Bitmap>(SIZE_CACHE){
			@Override
			protected int sizeOf(String key, Bitmap value) {
				int size = value.getWidth() * value.getHeight() * 4;
				return size;
			}
		};
	}
	
	public void bind(final String url, final ImageView imageView, final BaseAdapter adapter){
		mCurrentImageViewUrls.put(imageView.hashCode(), url);
		Bitmap bitmap = mLruCashe.get(url);
		{
			if(bitmap != null){
				imageView.setImageBitmap(bitmap);
				return;
			}
		}
		for (ImageLoadCallback callback : mUrlQueue) {
			if(callback.getUrl().equals(url)){
				mUrlQueue.remove(callback);
				pushCallback(callback);
				checkExecutorService();
				return;
			}
		}
		pushCallback(new ImageLoadCallback() {
			
			@Override
			public void onSuccess(Bitmap bm) {
				String currentImageViewUrl = mCurrentImageViewUrls.get(imageView.hashCode());
				if(currentImageViewUrl.equals(getUrl())){
					imageView.setImageBitmap(bm);
				}
				mUrlQueue.remove(getUrl());
				checkExecutorService();
			}
			
			@Override
			public void onError(Exception e) {
				Log.d(DropboxService.TAG, e.getMessage());
				checkExecutorService();
			}
			
			@Override
			public String getUrl() {
				return url;
			}
		});
		checkExecutorService();
	}
	
	private void checkExecutorService() {
		if(((ThreadPoolExecutor) mExecutorService).getActiveCount() < POOL_SIZE && !lock){
			proceed();
		}
			
	}

	private void pushCallback(ImageLoadCallback callback){
		if(mUrlQueue.size() > QUEUE_LIMIT){
			removeOldestCallback();
		}
		mUrlQueue.add(callback);
	}
	
	private void removeOldestCallback() {
		mUrlQueue.remove(0);
	}

	private ImageLoadCallback removeCallback(){
		return mUrlQueue.remove(mUrlQueue.size() - 1);
	}

	private void proceed(){
		if(mUrlQueue.isEmpty()){
			return;
		}
		final ImageLoadCallback callback = removeCallback();
		mExecutorService.execute(new Runnable() {
			
			@Override
			public void run() {
				boolean cachedInCasheDir = false;
				String url = callback.getUrl();
				String name = MD5.code(url) + ".jpeg";
				final Bitmap bitmap;
				try{
					if(cheakFileCache(name)){
						cachedInCasheDir = true;
						bitmap = getCachedBitmap(name);
					}else{
						bitmap = loadBitmap(url);
					}
					if(bitmap != null){
						mImageHandler.post(new Runnable() {	
							@Override
							public void run() {
								callback.onSuccess(bitmap);
							}
						});
						addCasheBitmap(url, name, bitmap, cachedInCasheDir);
						return;
					}else{
						throw new Exception("Some errors = (");
					}
				}catch(final Exception e){
					mImageHandler.post(new Runnable() {
						
						@Override
						public void run() {
							callback.onError(e);
						}
					});
				}
			}

			private void addCasheBitmap(String url, String name, Bitmap bitmap,
					boolean cachedInCasheDir) throws Exception {
				mLruCashe.put(url, bitmap);
				if(!cachedInCasheDir){
					new Runnable() {
						
						@Override
						public void run() {
							
						}
					};
					FileOutputStream os = null;
					try {
						File file = new File(mContext.getCacheDir(), name);
						file.createNewFile();
						os = new FileOutputStream(file);
						bitmap.compress(Bitmap.CompressFormat.JPEG, 90, os);
					} catch (FileNotFoundException e) {
						throw e;
					} finally{
						if(os != null){
							try {
								os.close();
							} catch (Exception e) {
								throw new Exception("Error while closing stream: " + e.getMessage());
							}
						}
					}
				}
				
			}

			private Bitmap loadBitmap(String url) throws Exception {
				InputStream fileContent = null;
				try {
					HttpGet request = new HttpGet(url);
					Signer.sign(request);
					fileContent = HttpManager.get(mContext).loadFileContent(request);
					return BitmapFactory.decodeStream(fileContent);
				} catch (Exception e) {
					throw new Exception("Error while loading from internet: " + e.getMessage());	
				} finally {
					if (fileContent != null){
						try {
							fileContent.close();
						} catch (Exception e) {
							throw new Exception("Error while closing stream: " + e.getMessage());
						}
					}
				}
			}
			
			protected Bitmap getCachedBitmap(String name) throws Exception{
				FileInputStream fis = null;
				try {
					File file = new File(mContext.getCacheDir(), name);
					fis = new FileInputStream(file);
					Bitmap bitmap = BitmapFactory.decodeStream(fis);
					return bitmap;
				} catch (FileNotFoundException e) {
					throw new FileNotFoundException("Bitmap's file not found in file cashe");
				} finally {
					if (fis != null){
						try {
							fis.close();
						} catch (Exception e) {
							throw new Exception("Error while closing stream: " + e.getMessage());
						}
					}
				}
			}

			protected boolean cheakFileCache(String name) {
				File file = new File(mContext.getCacheDir(), name);
				if(file.exists() && file.length() != 0){
					return true;
				}
				return false;
			}
		});
	}	

	
	public static ImageLoader get(Context ctx){
		ImageLoader imageLoader = (ImageLoader) ctx.getSystemService(IMAGE_LOADER);
		if(imageLoader != null){
			return imageLoader;
		}
		imageLoader = (ImageLoader) ctx.getApplicationContext().getSystemService(IMAGE_LOADER);
		if(imageLoader != null){
			return imageLoader;
		}
		throw new IllegalStateException("Service " + ImageLoader.class + " is not available");
	}	
}
